import asyncio

from common.third_party.devmem import DevMemCache, DevMem
from services.control.api import ControlApi
from datetime import timezone, timedelta, datetime
from services.control.schemas.control_registers import ControlStatesInfo
from services.ocb.schemas.ocb_registers import OcbThresholds, OcbStatesInfo, OcbCurrents28vInfo, OcbStates, \
    OcbCurrents28v, OcbThresholdsInfo
from services.ad936x.api import Ad936xApi, DoubleAttenuation
from services.server_integration.api import ServerIntegrationApi
from services.automatic_control.api import AutomaticControlApi
from services.services_module.api import ServicesModuleApi
from services.relays_module.api import RelaysModuleApi
from common.third_party.save_config import save_config, load_config
from settings import settings


def clamp(max_value: float, min_value: float, num: float, ):
    return max(min(num, max_value), min_value)

class Ocb:
    __slots__ = ['ocb_states', 'ocb_states_info', 'ocb_currents_28v', 'ocb_currents_28v_info',
                 'ocb_thresholds', 'ocb_thresholds_info', 'mem', 'rms_services_ocb',
                 'ocb_in_work', 'control_states_info']

    VOLTAGE_COEFFICIENT: float = 0.5
    ADC_BIT_DEPTH: int = 12
    CONVERSION_COEFFICIENT: int = 1
    VOLTAGE_MINIMAL_VALUE: int = 0
    VOLTAGE_MAXIMAL_VALUE: float = 6.9
    ADC_MINIMAL_VALUE: int = 0
    ADC_MAXIMAL_VALUE: int = 4095

    AD_API = Ad936xApi()
    CTRL_API = ControlApi()
    SI_API = ServerIntegrationApi()
    AC_API = AutomaticControlApi()
    SM_API = ServicesModuleApi()
    RM_API = RelaysModuleApi()

    def __init__(self) -> None:
        self.mem = DevMemCache()

        self.ocb_states = OcbStates()
        self.ocb_states_info = OcbStatesInfo()

        self.ocb_currents_28v = OcbCurrents28v()
        self.ocb_currents_28v_info = OcbCurrents28vInfo()

        self.ocb_thresholds = load_config('ocb/ocb_thresholds', OcbThresholds, OcbThresholds())
        self.ocb_thresholds_info = OcbThresholdsInfo()

        self.rms_services_ocb = [False] * 6
        self.ocb_in_work = [False] * 7

        self.control_states_info = ControlStatesInfo()

        if not settings.DEBUG:
            for name in self.ocb_states.dict().keys():
                self.ocb_states.set(name, self.read_state(name))

            for name in self.ocb_currents_28v.dict().keys():
                self.ocb_currents_28v.set(name, self.read_current28v(name))

            for name, value in self.ocb_thresholds.dict().items():
                self.write_threshold(name, value)

    async def telemetry_reboot_rm(self, index: int):
        await asyncio.sleep(55)
        control_reg_name = (
            'link_0_state', 'link_1_state'
        )[index]

        tray_number = 0
        while True:
            await asyncio.sleep(10)
            try:
                if not (await self.get_si_rm_state(index)):
                    continue

                rm_telemetry = await self.RM_API.get_telemetry(index)
                for _ in range(15):
                    if rm_telemetry.voltage_28v >= 2 and rm_telemetry.lna_5v >= 0.2:
                        tray_number = 0
                        break

                    rm_telemetry = await self.RM_API.get_telemetry(index)
                    await asyncio.sleep(4.3)
                else:
                    tray_number += 1

                    tz = await self.SI_API.get_timezone()
                    tz = timezone(timedelta(hours=tz))
                    tm = datetime.now(tz)

                    if tray_number > 3 and not(tm.hour == 2 and tm.minute < 1.08):
                        await asyncio.sleep(60 * 5)
                        continue

                    await self.SI_API.raise_info(f'Rm{index + 1}InvalidTelemetry', rm_telemetry.voltage_28v,
                                           rm_telemetry.lna_5v)
                    self.write_control_state(control_reg_name, False)
                    await asyncio.sleep(6)
                    self.write_control_state(control_reg_name, True)

                    match tray_number:
                        case 1:
                            await asyncio.sleep(22)
                        case 2:
                            await asyncio.sleep(60 * 30)
                        case _:
                            await asyncio.sleep(60 * 5)
            except:
                await asyncio.sleep(8)


    async def telemetry_reboot_sm(self, index: int):
        await asyncio.sleep(55)
        if self.read_control_state('block_state'):
            return
        
        control_reg_name = (
            'service_0_state', 'service_1_state', 'service_2_state', 'service_3_state', 'service_5v_state'
        )[index]

        tray_number = 0
        while True:
            await asyncio.sleep(10)
            try:
                if not (await self.get_si_service_state(index)):
                    continue

                sm_telemetry = await self.SM_API.get_telemetry(index)
                for _ in range(15):
                    if sm_telemetry.voltage_28v >= 2 and sm_telemetry.lna_5v >= 0.2:
                        tray_number = 0
                        break

                    sm_telemetry = await self.SM_API.get_telemetry(index)
                    await asyncio.sleep(4.3)
                else:
                    tray_number += 1
                    await self.SI_API.raise_info(f'Sm{index + 1}InvalidTelemetry', sm_telemetry.voltage_28v,
                                           sm_telemetry.lna_5v)

                    self.write_control_state(control_reg_name, False)
                    await asyncio.sleep(6)
                    self.write_control_state(control_reg_name, True)

                    match tray_number:
                        case 1:
                            await asyncio.sleep(22)
                        case 2:
                            await asyncio.sleep(60 * 30)
                        case 3:
                            await asyncio.sleep(60 * 60)
                        case 4:
                            await asyncio.sleep(60 * 60 * 3)
                        case _:
                            await asyncio.sleep(60 * 60 * 24)
            except:
                await asyncio.sleep(8)

    async def rm_loop(self, index: int):
        await asyncio.sleep(44.0)

        in_work_index = index
        ad_index = (0, 0)[index]
        attenuation_index = (0, 1)[index]
        ocb_reg_name = ('link_0_ocb', 'link_1_ocb')[index]
        control_reg_name = ('link_0_state', 'link_1_state')[index]

        target_attenuation = 0
        tray_number = 0
        while True:
            await asyncio.sleep(5.0)
            try:
                if not (await self.get_si_rm_state(index)):
                    self.ocb_in_work[in_work_index] = False
                    tray_number = 0
                    target_attenuation = 0
                    continue

                if not self.read_state(ocb_reg_name):
                    self.ocb_in_work[in_work_index] = False
                    tray_number = 0
                    target_attenuation = 0
                    continue

                await self.SI_API.raise_info(f'OcbWorkRelay{index + 1}', True, True)
                self.write_control_state(control_reg_name, False)

                tray_number += 1
                if tray_number >= 8:
                    await asyncio.sleep(60 * 60)
                else:
                    await asyncio.sleep(3.0 * (tray_number ** 2))

                self.ocb_in_work[in_work_index] = True
                ad_config = await self.AD_API.get_config(ad_index)
                current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

                if target_attenuation == 0:
                    target_attenuation = current_attenuation[attenuation_index]
                    current_attenuation[attenuation_index] += 3
                else:
                    current_attenuation[attenuation_index] = target_attenuation + 6

                current_attenuation[attenuation_index] = clamp(
                    52, 5,
                    current_attenuation[attenuation_index]
                )

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0],
                    attenuation_1=current_attenuation[1]
                ))
                await asyncio.sleep(4.0)

                self.write_control_state(control_reg_name, True)
                await asyncio.sleep(8.2)

            except Exception:
                pass

    async def sm_loop(self, index: int):
        # await asyncio.sleep(44.0)
        if self.read_control_state('block_state'):
            return

        in_work_index = index + 2
        ad_index = (1, 1, 2, 2, -1)[index]
        attenuation_index = (1, 0, 1, 0, -1)[index]
        ocb_reg_name = (
            'service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb', 'service_5v_ocb'
        )[index]
        control_reg_name = (
            'service_0_state', 'service_1_state', 'service_2_state', 'service_3_state', 'service_5v_state'
        )[index]

        target_attenuation = 0
        tray_number = 0
        while True:
            await asyncio.sleep(5.0)
            try:
                if not self.read_state(ocb_reg_name):
                    self.ocb_in_work[in_work_index] = False
                    tray_number = 0
                    target_attenuation = 0
                    continue

                if not (await self.get_si_service_state(index)):
                    self.write_control_state(control_reg_name, False)
                    self.ocb_in_work[in_work_index] = False
                    tray_number = 0
                    target_attenuation = 0
                    continue

                self.write_control_state(control_reg_name, False)
                self.ocb_in_work[in_work_index] = True

                if index < 4:
                    await self.SI_API.raise_info(f'OcbWorkSm{index + 1}', True, True)
                    prev_sm_scale = self.read_sm_scale(index)
                    self.write_sm_scale(index, 0)
                else:
                    prev_sm_scale = 0

                await asyncio.sleep(3)

                old_threshold = None
                if self.read_current28v(ocb_reg_name) > 2.0:
                    old_threshold = self.read_threshold(ocb_reg_name)
                    self.write_threshold(ocb_reg_name, self.read_current28v(ocb_reg_name) + 1.2)

                if ad_index == -1:
                    await asyncio.sleep(17)
                    self.write_control_state(control_reg_name, True)
                    continue

                ad_config = await self.AD_API.get_config(ad_index)
                current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

                if target_attenuation == 0:
                    target_attenuation = current_attenuation[attenuation_index]
                    current_attenuation[attenuation_index] += 3
                else:
                    current_attenuation[attenuation_index] = target_attenuation + 6

                current_attenuation[attenuation_index] = clamp(
                    52, 5,
                    current_attenuation[attenuation_index]
                )

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0],
                    attenuation_1=current_attenuation[1]
                ))
                await asyncio.sleep(4.0)

                self.write_control_state(control_reg_name, True)
                await asyncio.sleep(3)
                self.write_control_state(control_reg_name, False)
                await asyncio.sleep(3)
                self.write_control_state(control_reg_name, True)
                await asyncio.sleep(3)

                if old_threshold is not None:
                    self.write_threshold(ocb_reg_name, old_threshold)

                if index < 4:
                    self.write_sm_scale(index, prev_sm_scale)
                    await asyncio.sleep(4.4)

                if not self.read_state(ocb_reg_name):
                    await self.AC_API.enforce_sm_tx_ac(index, 3)

                tray_number += 1
                if tray_number >= 16:
                    self.write_control_state(control_reg_name, False)
                    for _ in range(60):
                        await asyncio.sleep(60)
                        if self.read_control_state(control_reg_name):
                            self.write_control_state(control_reg_name, True)
                            await asyncio.sleep(3)
                            self.write_control_state(control_reg_name, True)
                            break

                    self.write_control_state(control_reg_name, True)
                else:
                    await asyncio.sleep(3.0 * (tray_number ** 2))
            except BaseException:
                pass

    def write_sm_scale(self, index: int, value: int):
        address =  0x83C6_0000
        offset = 0x4
        mem = DevMem(address=address, number_of_words=256, word_size=1)

        mem.write(offset=offset + index, value=value)

    def read_sm_scale(self, index: int) -> int:
        address = 0x83C6_0000
        offset = 0x4
        mem = DevMem(address=address, number_of_words=256, word_size=1)

        return mem.read(offset + index).get_value()

    async def ocb_currents_loop(self):
        while True:
            for name in self.ocb_currents_28v.dict().keys():
                self.ocb_currents_28v.set(name, self.read_current28v(name))

            await asyncio.sleep(0.93)

    async def get_si_service_state(self, index: int):
        config = await self.SI_API.get_full_configuration()

        if index == 4:
            return any([
                config.service_channels[0].str.enabled,
                config.service_channels[1].str.enabled,
                config.service_channels[2].str.enabled,
                config.service_channels[3].str.enabled,
            ])

        return config.service_channels[index].str.enabled

    async def get_si_rm_state(self, index: int):
        config = await self.SI_API.get_full_configuration()
        return  config.relay_channels[0].rtr_common.rt_rxs_enabled[index]



    # async def press_service_rms_ocb(self, index: int):
    #     name = [
    #         'link_0_ocb', 'link_1_ocb',
    #         'service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb'
    #     ][index]
    #     control_state_name = [
    #         'link_0_state', 'link_1_state',
    #         'service_0_state', 'service_1_state', 'service_2_state', 'service_3_state'
    #     ][index]
    #
    #     if not self.CTRL_API.get_control_state(control_state_name):
    #         return None
    #
    #     if not self.read_state(name):
    #         self.rms_services_ocb[index] = False
    #         return False
    #
    #     self.rms_services_ocb[index] = True
    #     for _ in range(3):
    #         self.CTRL_API.set_control_state(control_state_name, False)
    #         await asyncio.sleep(3.0)
    #         self.CTRL_API.set_control_state(control_state_name, True)
    #
    #         await asyncio.sleep(3.0)
    #         if not self.read_state(name):
    #             return False
    #
    #     ad_index = [0, 0, 1, 1, 2, 2][index]
    #     attenuation_index = [0, 1, 1, 0, 1, 0][index]
    #
    #     ad_config = self.AD_API.get_config(ad_index)
    #     current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]
    #     prev_attenuation = current_attenuation[attenuation_index]
    #     current_attenuation[attenuation_index] += 10
    #
    #     self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
    #         attenuation_0=current_attenuation[0],
    #         attenuation_1=current_attenuation[1]
    #     ))
    #     self.CTRL_API.set_control_state(control_state_name, False)
    #     await asyncio.sleep(3.0)
    #     self.CTRL_API.set_control_state(control_state_name, True)
    #     await asyncio.sleep(3.0)
    #     if self.read_state(name):
    #         return True
    #
    #     if self._is_gsm_mode() and index in [4, 5]:
    #         return False
    #
    #     while current_attenuation[attenuation_index] != prev_attenuation:
    #         current_attenuation[attenuation_index] -= 1
    #         current_attenuation[attenuation_index] = min(max(current_attenuation[attenuation_index], 0), 70)
    #
    #         try:
    #             self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
    #                 attenuation_0=current_attenuation[0],
    #                 attenuation_1=current_attenuation[1]
    #             ))
    #             await asyncio.sleep(3.0)
    #             if self.read_state(name):
    #                 return True
    #
    #         except Exception as e:
    #             return True
    #
    #     return False

    # def _is_gsm_mode(self):
    #     try:
    #         config = self.SI_API.get_full_configuration()
    #         return config.unit_configuration.channel_3_and_4_config == ChanelConfig.GSM
    #     except Exception:
    #         return False



    # def set_si_service_state(self, index, state):
    #     config = self.SI_API.get_full_configuration()
    #     config.service_channels[index].str.enabled = state
    #     self.SI_API.set_full_configuration(config)
    #
    # def set_si_rm_state(self, index, state):
    #     config = self.SI_API.get_full_configuration()
    #     for i in range(4):
    #         config.relay_channels[i].rtr_common.rt_rxs_enabled[index] = state
    #
    #     self.SI_API.set_full_configuration(config)

    def read_services_ocb(self):
        return [
            self.read_state(name)
            for name in ['service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb']
        ]

    def read_state(self, name: str) -> bool:
        info = self.ocb_states_info.get(name)
        if not settings.DEBUG:
            mem = self.mem.get(info.address)
            return mem[info.offset]

        return self.ocb_states.get(name)

    def read_current28v(self, name: str) -> float:
        info = self.ocb_currents_28v_info.get(name)
        current = self._read_amperes_value(info.address, info.offset)
        return current if current is not None else self.ocb_currents_28v.get(name)

    def read_threshold(self, name: str) -> float:
        info = self.ocb_thresholds_info.get(name)
        current = self._read_amperes_value(info.address, info.offset)
        return current

    def write_threshold(self, name: str, value: float):
        info = self.ocb_thresholds_info.get(name)
        self._write_adc_value(info.address, info.offset, value)
        self.ocb_thresholds.set(name, value)
        save_config('ocb/ocb_thresholds', self.ocb_thresholds)

    def _read_amperes_value(self, address: int, offset: int):
        mem = self.mem.get(address)
        reg = mem.read(offset).get_value()
        return self._adc_to_amperes_value(reg)

    def _write_adc_value(self, address: int, offset: int, value: float):
        adc_value = self._amperes_to_adc_value(value)
        if not settings.DEBUG:
            mem = self.mem.get(address)
            mem.write(offset, adc_value)

    def _amperes_to_adc_value(self, adc_value: float) -> int:
        if not settings.DEBUG:
            if self.VOLTAGE_MINIMAL_VALUE <= adc_value <= self.VOLTAGE_MAXIMAL_VALUE:
                return round(((adc_value * self.VOLTAGE_COEFFICIENT) * (
                        2 ** self.ADC_BIT_DEPTH - self.CONVERSION_COEFFICIENT)) / 3.3)
            elif adc_value > self.VOLTAGE_MAXIMAL_VALUE:
                return round(self.ADC_MAXIMAL_VALUE)
            elif adc_value < self.VOLTAGE_MINIMAL_VALUE:
                return round(self.ADC_MINIMAL_VALUE)
        return round(self.ADC_MAXIMAL_VALUE)

    def _adc_to_amperes_value(self, reg: int) -> float:
        if self.VOLTAGE_MINIMAL_VALUE <= reg <= self.ADC_MAXIMAL_VALUE:
            return (
                    (
                        reg * 3.3 /
                        (2 ** self.ADC_BIT_DEPTH - self.CONVERSION_COEFFICIENT)
                    ) / self.VOLTAGE_COEFFICIENT
            )
        elif reg > self.ADC_MAXIMAL_VALUE:
            return self.VOLTAGE_MAXIMAL_VALUE
        elif reg < self.ADC_MINIMAL_VALUE:
            return self.VOLTAGE_MINIMAL_VALUE

    def read_control_state(self, name: str) -> bool:
        info = self.control_states_info.get(name)
        mem = self.mem.get(info.address)
        return mem[info.offset]

    def write_control_state(self, name: str, state: bool):
        info = self.control_states_info.get(name)
        mem = self.mem.get(info.address)
        mem[info.offset] = state